package com.gemantic.task.util;

import lombok.extern.slf4j.Slf4j;
import java.util.Date;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.JavaType;
import com.gemantic.task.config.scheduler.SchedulerTaskConfig;
import com.gemantic.task.dto.ResResult;
import com.gemantic.task.dto.TaskContext;
import com.gemantic.task.handler.DefaultTaskHandlerFactoryImpl;
import com.gemantic.task.handler.ITaskHandler;
import com.gemantic.task.handler.TaskHandlerFactory;
import com.google.common.collect.Maps;

/**
 * @author daishun
 * @since 2019/8/26
 */
@Slf4j
public class TaskUtil {

    public static final int OFFSET_SECOND = 60;
    private static final int POSTPONE_MINUTES = 30;
    private static final int MAX_POW_TIMES = 14;
    private static final int POW_INTERVAL_BASE = 2;
    private static final int MIN_INTERVAL_SECONDS = 5;

    private static TaskHandlerFactory taskHandlerFactory = null;
    private static SchedulerTaskConfig schedulerTaskConfig = null;

    private TaskUtil() {}

    public static Date getNextRunTime(TaskContext context, ResResult result) {
        int maxRetryTimes = context.getMaxRetryTimes();
        int maxIntervalSeconds = getMaxIntervalSeconds();
        return getNextRunTime(context, result, OFFSET_SECOND, maxIntervalSeconds, maxRetryTimes);
    }

    public static Date getNextRunTime(TaskContext context, ResResult result, int offsetSeconds) {
        int maxRetryTimes = context.getMaxRetryTimes();
        int maxIntervalSeconds = getMaxIntervalSeconds();
        return getNextRunTime(context, result, offsetSeconds, maxIntervalSeconds, maxRetryTimes);
    }

    /**
     * 获取下次执行时间
     *
     * @param context
     * @param result
     * @param offsetSeconds
     * @param maxIntervalSeconds
     * @return
     */
    public static Date getNextRunTime(TaskContext context, ResResult result, int offsetSeconds,
                                      int maxIntervalSeconds, int maxRetryTimes) {
        if (result != null
                && result.isPostpone()) {
            return DateUtil.addMinutes(POSTPONE_MINUTES);
        }
        int retryTimes = null == context.getRetryTimes() ? 0 : context.getRetryTimes();
        return getNextRunTime(retryTimes, offsetSeconds, maxIntervalSeconds, maxRetryTimes);
    }


    public static Date getNextRunTime(int retryTime) {
        int maxRetryTimes = getMaxRetryTimes();
        int maxIntervalSeconds = getMaxIntervalSeconds();
        return getNextRunTime(retryTime, OFFSET_SECOND, maxIntervalSeconds, maxRetryTimes);
    }

    public static Date getNextRunTime(int retryTime, int maxRetryTimes) {
        int maxIntervalSeconds = getMaxIntervalSeconds();
        return getNextRunTime(retryTime, OFFSET_SECOND, maxIntervalSeconds, maxRetryTimes);
    }

    /**
     * 获取下次执行时间。
     *
     * @param retryTime
     * @param offsetSeconds
     * @param maxIntervalSeconds
     * @param maxRetryTimes
     * @return
     */
    public static Date getNextRunTime(int retryTime, int offsetSeconds, int maxIntervalSeconds, int maxRetryTimes) {
        int seconds = getIntervalSecond(retryTime, offsetSeconds, maxIntervalSeconds, maxRetryTimes);
        if (seconds > 0) {
            return DateUtils.addSeconds(new Date(), seconds);
        }
        return null;
    }

    public static int getIntervalSecond(int retryTime, int offsetSeconds, int maxIntervalSeconds, int maxRetryTimes) {
        int seconds = 0;
        int offsets = Math.max(1, offsetSeconds);
        if (retryTime + 1 < maxRetryTimes) {
            //1,2,4,8,16...128
            //运行2^16*60  16次之后，会溢出.
            retryTime = Math.min(retryTime, MAX_POW_TIMES);
            seconds = offsets * (int) Math.pow(POW_INTERVAL_BASE, retryTime);
            seconds = Math.min(Math.max(MIN_INTERVAL_SECONDS, seconds), maxIntervalSeconds);
        }
        return seconds;
    }

    public static Date getNextRunTime(int retryTime, Date cutoffTime) {
        int maxRetryTimes = getMaxRetryTimes();
        int maxIntervalSeconds = getMaxIntervalSeconds();
        return getNextRunTime(retryTime, cutoffTime, OFFSET_SECOND, maxIntervalSeconds, maxRetryTimes);
    }

    /**
     * 推算下次执行时间
     *
     * @param retryTime
     * @param cutoffTime
     * @param offsetSeconds
     * @param maxIntervalSeconds
     * @param maxRetryTimes
     * @return
     */
    public static Date getNextRunTime(int retryTime, Date cutoffTime, int offsetSeconds,
                                      int maxIntervalSeconds, int maxRetryTimes) {
        Date nextRunDate = getNextRunTime(retryTime, offsetSeconds, maxIntervalSeconds, maxRetryTimes);
        if (nextRunDate != null) {
            if (cutoffTime != null && nextRunDate.after(cutoffTime)) {
                if (cutoffTime.before(DateUtil.now())) {
                    return null;
                }
                return cutoffTime;
            }
            return nextRunDate;
        }
        return null;
    }


    /**
     * 获取task handler 工厂方法
     *
     * @return
     */
    public static synchronized TaskHandlerFactory getTaskHandlerFactory() {
        if (taskHandlerFactory == null) {
            if (SpringUtils.containsBean(TaskHandlerFactory.TASK_HANDLER_FACTORY_BEAN_NAME)) {
                taskHandlerFactory = SpringUtils.getBean(TaskHandlerFactory.TASK_HANDLER_FACTORY_BEAN_NAME,
                        TaskHandlerFactory.class);
            } else {
                taskHandlerFactory = new DefaultTaskHandlerFactoryImpl();
            }
        }
        return taskHandlerFactory;
    }

    public static synchronized SchedulerTaskConfig getSchedulerTaskConfig() {
        if (schedulerTaskConfig == null) {
            schedulerTaskConfig = SpringUtils.getBean(SchedulerTaskConfig.class);
        }
        return schedulerTaskConfig;
    }


    public static Map<String, String> createTaskArgs(String runnerName) {
        Map<String, String> args = Maps.newHashMap();
        args.put("runnerName", runnerName);
        return args;
    }

    /**
     * 获取TaskType
     *
     * @param taskClass
     * @param <T>
     * @return
     */
    public static <T extends ITaskHandler> String getTaskType(Class<T> taskClass) {
        Assert.notNull(taskClass, "taskhandler class不能为null");

        String taskType = null;
        Service serviceProperty = taskClass.getAnnotation(Service.class);
        if (serviceProperty != null) {
            taskType = serviceProperty.value();
        }
        if (StringUtils.isEmpty(taskType)) {
            taskType = taskClass.getSimpleName().replace("TaskHandler", StringUtils.EMPTY);
        }
        return taskType;
    }

    public static Object getProcessData(String reqData, Class<? extends ITaskHandler> handler) {
        JavaType javaType = JavaTypeFactory.constructType(handler.getClass());
        JavaType paramJavaType = javaType.findTypeParameters(handler)[0];
        return JSON.parseObject(reqData, paramJavaType);
    }

    public static int getMaxRetryTimes() {
        return getSchedulerTaskConfig().getTaskMaxRetryTimes();
    }

    public static int getMaxIntervalSeconds() {
        return getSchedulerTaskConfig().getTaskMaxIntervalSeconds();
    }

    public static int getMaxDateSeconds() {
        return getSchedulerTaskConfig().getTaskMaxDateSeconds();
    }
}
